import { TYPES } from "@main/shared/types";
import { extractErrorMessage } from "@main/utils/error-utils";
import logger from "@shared/logger/main-logger";
import type {
  CreateThreadData,
  Message,
  Thread,
  UpdateThreadData,
} from "@shared/triplit/types";
import { inject, injectable } from "inversify";
import {
  CommunicationWay,
  ServiceHandler,
  ServiceRegister,
} from "../shared/reflect";
import type { ThreadDbService } from "./db-service/thread-db-service";
import { EventNames, emitter, sendToMain } from "./event-service";
import type { McpService } from "./mcp-service";
import type { ModelService } from "./model-service";
import type { ProviderService } from "./provider-service";
import type { SettingsService } from "./settings-service";

@ServiceRegister(TYPES.ThreadService)
@injectable()
export class ThreadService {
  constructor(
    @inject(TYPES.ThreadDbService) private threadDbService: ThreadDbService,
    @inject(TYPES.ProviderService) private providerService: ProviderService,
    @inject(TYPES.SettingsService) private settingsService: SettingsService,
    @inject(TYPES.ModelService) private modelService: ModelService,
    @inject(TYPES.McpService) private mcpService: McpService,
  ) {
    emitter.on(EventNames.PROVIDER_DELETE, ({ providerId }) => {
      this.resetThreadByProviderId(providerId);
    });
    emitter.on(EventNames.MESSAGE_ACTIONS, ({ threadId }) => {
      this.threadDbService.updateThread(threadId, undefined, true);
    });
    emitter.on(
      EventNames.MESSAGE_SEND_FROM_USER,
      ({ threadId, selectedMcpServerIds }) => {
        this.threadDbService.updateThread(threadId, undefined, true);

        // If we have selectedMcpServerIds, associate them with the thread
        if (selectedMcpServerIds && selectedMcpServerIds.length > 0) {
          // Set the MCP servers for this thread asynchronously
          this.setThreadMcpServers(threadId, selectedMcpServerIds).catch(
            (error) => {
              logger.error(
                "Failed to set MCP servers for thread in event handler",
                {
                  threadId,
                  selectedMcpServerIds,
                  error,
                },
              );
            },
          );
        }
      },
    );
    emitter.on(
      EventNames.PROVIDER_CONVERSATION_COMPLETED,
      ({ threadId, regeneration }) => {
        this.handleProviderConversationCompleted(threadId, regeneration);
      },
    );
  }

  private async handleProviderConversationCompleted(
    threadId: string,
    regeneration: boolean,
  ) {
    const titleGenerationTiming =
      await this.settingsService.getTitleGenerationTiming();
    if (!titleGenerationTiming) return;

    switch (titleGenerationTiming) {
      case "first-round": {
        const messages =
          await this.threadDbService.getMessagesByThreadId(threadId);
        const assistantMessages = messages.filter(
          (msg) => msg.role === "assistant",
        );
        const isFirstRound = !regeneration && assistantMessages.length === 1;
        if (isFirstRound) {
          await this._summaryTitle(threadId, false);
        }
        break;
      }
      case "every-round":
        await this._summaryTitle(threadId, false);
        break;
      case "off":
        break;
    }
  }

  private async _summaryTitle(
    threadId: string,
    manual: boolean,
  ): Promise<{
    isOk: boolean;
    errorMsg: string | null;
  }> {
    let modelName = "";
    let providerId = "";

    const [
      { modelName: threadModelName, providerId: threadProviderId, messages },
      titleModelId,
    ] = await Promise.all([
      this.threadDbService.getTitleSummaryParams(threadId),
      this.settingsService.getTitleModelId(),
    ]);

    const isUseCurrentChatModel = titleModelId === "use-current-chat-model";
    if (!isUseCurrentChatModel) {
      const model = await this.modelService._getModelById(titleModelId);
      if (!model) {
        return {
          isOk: false,
          errorMsg: "Model not found",
        };
      }
      modelName = model.name;
      providerId = model.providerId || "";
    }

    const { isOk, errorMsg, title } = await this.providerService.summaryTitle(
      isUseCurrentChatModel ? threadModelName : modelName,
      isUseCurrentChatModel ? threadProviderId : providerId,
      messages,
    );

    if (!isOk) {
      return {
        isOk: false,
        errorMsg,
      };
    }

    await this.threadDbService.updateThread(threadId, { title }, manual);

    sendToMain(EventNames.THREAD_RENAME, {
      threadId,
      newTitle: title,
    });

    return {
      isOk: true,
      errorMsg: null,
    };
  }

  private async resetThreadByProviderId(providerId: string) {
    try {
      const threads = await this.threadDbService.getThreads();
      for (const thread of threads) {
        if (thread.providerId === providerId) {
          await this.threadDbService.updateThread(thread.id, {
            providerId: "",
            modelId: "",
          });
        }
      }
    } catch (error) {
      logger.error("ThreadService:resetProviderId error", { error });
      throw error;
    }
  }

  async getThreads(): Promise<Thread[]> {
    const threads = await this.threadDbService.getThreads();
    return threads;
  }

  async _getThreadById(threadId: string): Promise<Thread | null> {
    return await this.threadDbService.getThreadById(threadId);
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async insertThread(
    _event: Electron.IpcMainEvent,
    thread: CreateThreadData,
  ): Promise<Thread> {
    try {
      const newThread = await this.threadDbService.insertThread(thread);
      return newThread;
    } catch (error) {
      logger.error("ThreadService:insertThread error", { error });
      throw error;
    }
  }

  async _createNewThread(
    thread: CreateThreadData,
    triggerBy?: "first-send-message" | "without-send-message",
    isNewTab?: boolean,
  ): Promise<Thread> {
    try {
      const newThread = await this.threadDbService.insertThread(thread);
      sendToMain(EventNames.THREAD_CREATED, {
        threadId: newThread.id,
        triggerBy: triggerBy ?? "first-send-message",
        isNewTab,
      });
      return newThread;
    } catch (error) {
      logger.error("ThreadService:insertThread error", { error });
      throw error;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async createNewThread(
    _event: Electron.IpcMainEvent,
    thread: CreateThreadData,
    triggerBy?: "first-send-message" | "without-send-message",
  ): Promise<Thread> {
    try {
      const newThread = await this.threadDbService.insertThread(thread);
      sendToMain(EventNames.THREAD_CREATED, {
        threadId: newThread.id,
        triggerBy: triggerBy ?? "first-send-message",
      });
      return newThread;
    } catch (error) {
      logger.error("ThreadService:insertThread error", { error });
      throw error;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async deleteThread(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<void> {
    try {
      await this.threadDbService.deleteThread(threadId);

      sendToMain(EventNames.THREAD_DELETED, { threadId });
    } catch (error) {
      logger.error("ThreadService:deleteThread error", { error });
      throw error;
    }
  }

  async _updateThread(
    threadId: string,
    updateData: UpdateThreadData,
  ): Promise<void> {
    await this.threadDbService.updateThread(threadId, updateData);
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getThreadById(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<Thread | null> {
    try {
      const thread = await this.threadDbService.getThreadById(threadId);
      return thread;
    } catch (error) {
      logger.error("ThreadService:getThreadById error", { error });
      throw error;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async deleteAllThreads(): Promise<string[]> {
    try {
      const threadIds = await this.threadDbService.deleteAllThreads();
      sendToMain(EventNames.THREAD_DELETED_ALL, null);
      return threadIds;
    } catch (error) {
      logger.error("ThreadService:deleteAllThreads error", { error });
      throw error;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async updateThreadCollected(
    _event: Electron.IpcMainEvent,
    threadId: string,
    collected: boolean,
  ): Promise<{
    isOk: boolean;
    errorMsg: string | null;
  }> {
    try {
      await this.threadDbService.updateThread(threadId, {
        collected,
      });
      return {
        isOk: true,
        errorMsg: null,
      };
    } catch (error) {
      return {
        isOk: false,
        errorMsg: extractErrorMessage(error),
      };
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async updateThreadTitle(
    _event: Electron.IpcMainEvent,
    threadId: string,
    title: string,
  ): Promise<void> {
    await this.threadDbService.updateThread(threadId, { title }, true);
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async updateThreadPrivate(
    _event: Electron.IpcMainEvent,
    threadId: string,
    isPrivate: boolean,
  ): Promise<void> {
    await this.threadDbService.updateThread(threadId, { isPrivate });
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async updateThreadModel(
    _event: Electron.IpcMainEvent,
    threadId: string,
    modelId: string,
    providerId: string,
  ): Promise<void> {
    await this.threadDbService.updateThread(threadId, {
      modelId,
      providerId,
    });
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async summaryTitle(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<{
    isOk: boolean;
    errorMsg: string | null;
  }> {
    return this._summaryTitle(threadId, true);
  }

  /**
   * Set MCP servers for a thread (internal method)
   */
  private async setThreadMcpServers(
    threadId: string,
    serverIds: string[],
  ): Promise<void> {
    try {
      logger.info("Setting MCP servers for thread from ThreadService", {
        threadId,
        serverIds,
      });
      await this.mcpService.setThreadMcpServers(
        {} as Electron.IpcMainEvent,
        threadId,
        serverIds,
      );
    } catch (error) {
      logger.error("Failed to set MCP servers for thread", {
        threadId,
        serverIds,
        error,
      });
      throw error;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async createNewBranch(
    _event: Electron.IpcMainEvent,
    message: Message,
    branchTitle: string,
    modelId: string,
    providerId: string,
    isPrivate: boolean,
    triggerBy?: "first-send-message" | "without-send-message",
    isNewTab?: boolean,
  ): Promise<{ threadId: string } | null> {
    try {
      const thread = await this._createNewThread(
        {
          title: branchTitle,
          modelId,
          providerId,
          isPrivate,
        },
        triggerBy,
        isNewTab,
      );

      if (!thread) {
        logger.error("Failed to create thread for branch");
        return null;
      }

      const { id: threadId, title } = thread;

      sendToMain(EventNames.THREAD_NEW_BRANCH, {
        title,
        originalThreadId: message.threadId,
        newThreadId: threadId,
        type: "thread",
        isPrivate,
        messageId: message.id,
        isNewTab,
      });

      // await this.uiService._updateActiveThreadId(thread.id);

      return { threadId };
    } catch (error) {
      logger.error("Error creating new branch:", { error });
      throw error;
    }
  }
}
